package com.yanmaohu.yulia;

import com.yanmaohu.yulia.token.Token;
import com.yanmaohu.yulia.token.TokenType;

import java.io.*;

/**
 * @author Bright Lee
 */
public final class Scanner {

    private static final int BUFFER_INIT_CAPACITY = 1000;

    // 行号：
    private int lineNumber = 1;
    // 缓存区：
    private StringBuilder buffer = new StringBuilder(BUFFER_INIT_CAPACITY);
    // 当前缓存区在整个输入串中的位置：
    private int bufferBeginIndex = 0;
    // 当前字符在缓存区中的位置：
    private int bufferCurrentIndex = 0;
    // 当前token在缓存区中的位置：
    private int bufferTokenBeginIndex = 0;

    private Reader reader;

    public Scanner(Reader reader) {
        if (reader == null) {
            throw new RuntimeException("字符流不能为空。");
        }
        this.reader = reader;
        load();
    }

    public Scanner(InputStream in, String charseName) {
        if (in == null) {
            throw new RuntimeException("字节流不能为空。");
        }
        try {
            reader = new InputStreamReader(in, charseName);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("构造字符流失败。", e);
        }
        load();
    }

    public Scanner(InputStream in) {
        this(in, "UTF-8");
    }

    public Scanner(String sql) {
        if (sql == null) {
            sql = "";
        }
        reader = new StringReader(sql);
        load();
    }

    public Token scan() {
        int c;
        while (true) {
            c = skip();
            if (c < 0) {
                int beginIndex = bufferBeginIndex + buffer.length();
                int endIndex = beginIndex;
                return new Token(TokenType.EOF, beginIndex, endIndex);
            }
            if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') {
                while (true) {
                    c = read();
                    if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && c != '_') {
                        back();
                        break;
                    }
                }
                String content = buffer.substring(bufferTokenBeginIndex, bufferCurrentIndex);
                TokenType tokenType = TokenType.getReservedWordTokenType(content);
                if (tokenType == null) {
                    tokenType = TokenType.REGULAR_IDENTIFIER;
                }
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(tokenType, beginIndex, endIndex, content);
            } else if (c >= '0' && c <= '9') {
                if (c != '0') {
                    while (true) {
                        c = read();
                        if (c < '0' || c > '9') {
                            back();
                            break;
                        }
                    }
                } else {
                    back();
                }
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                String content = buffer.substring(bufferTokenBeginIndex, bufferCurrentIndex);
                return new Token(TokenType.UNSIGNED_INTEGER_LITERAL, beginIndex, endIndex, content);
            } else if (c == '"') {
                while (true) {
                    c = read();
                    if (c < 0 || c == '\r' || c == '\n') {
                        throw new YuliaException(getSql(), getLineNumber(), bufferCurrentIndex - 1, "字符串意外结束。");
                    }
                    if (c == '"') {
                        break;
                    }
                }
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                String content = buffer.substring(bufferTokenBeginIndex + 1, bufferCurrentIndex - 1);
                return new Token(TokenType.UNICODE_DELIMITER_BODY, beginIndex, endIndex, content);
            } else if (c == '%') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.PERCENT, beginIndex, endIndex);
            } else if (c == '&') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.AMPERSAND, beginIndex, endIndex);
            } else if (c == '\'') {
                while (true) {
                    c = read();
                    if (c < 0 || c == '\r' || c == '\n') {
                        throw new YuliaException(getSql(), getLineNumber(), bufferCurrentIndex - 1, "字符串意外结束。");
                    }
                    if (c == '\'') {
                        c = read();
                        if (c == '\'') {
                            continue;
                        }
                        back();
                        break;
                    }
                }
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                String content = buffer.substring(bufferTokenBeginIndex + 1, bufferCurrentIndex - 1);
                return new Token(TokenType.STRING_LITERAL, beginIndex, endIndex, content);
            } else if (c == '(') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.LEFT_PAREN, beginIndex, endIndex);
            } else if (c == ')') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.RIGHT_PAREN, beginIndex, endIndex);
            } else if (c == '*') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.ASTERISK, beginIndex, endIndex);
            } else if (c == '+') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.PLUS_SIGN, beginIndex, endIndex);
            } else if (c == ',') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.COMMA, beginIndex, endIndex);
            } else if (c == '-') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.MINUS_SIGN, beginIndex, endIndex);
            } else if (c == '.') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.PERIOD, beginIndex, endIndex);
            } else if (c == '/') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.SOLIDUS, beginIndex, endIndex);
            } else if (c == ':') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.COLON, beginIndex, endIndex);
            } else if (c == ';') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.SEMICOLON, beginIndex, endIndex);
            } else if (c == '<') {
                c = read();
                if (c == '=') {
                    int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                    int endIndex = bufferBeginIndex + bufferCurrentIndex;
                    return new Token(TokenType.LESS_THAN_OR_EQUALS_OPERATOR, beginIndex, endIndex);
                }
                if (c == '>') {
                    int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                    int endIndex = bufferBeginIndex + bufferCurrentIndex;
                    return new Token(TokenType.NOT_EQUALS_OPERATOR, beginIndex, endIndex);
                }
                back();
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.LESS_THAN_OPERATOR, beginIndex, endIndex);
            } else if (c == '=') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.EQUALS_OPERATOR, beginIndex, endIndex);
            } else if (c == '>') {
                c = read();
                if (c == '=') {
                    int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                    int endIndex = bufferBeginIndex + bufferCurrentIndex;
                    return new Token(TokenType.GREATER_THAN_OR_EQUALS_OPERATOR, beginIndex, endIndex);
                }
                back();
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.GREATER_THAN_OPERATOR, beginIndex, endIndex);
            } else if (c == '?') {
                c = read();
                if (c == '?') {
                    c = read();
                    if (c == '(') {
                        int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                        int endIndex = bufferBeginIndex + bufferCurrentIndex;
                        return new Token(TokenType.LEFT_BRACKET_TRIGRAPH, beginIndex, endIndex);
                    }
                    if (c == ')') {
                        int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                        int endIndex = bufferBeginIndex + bufferCurrentIndex;
                        return new Token(TokenType.RIGHT_BRACKET_TRIGRAPH, beginIndex, endIndex);
                    }
                    back();
                    back();
                } else {
                    back();
                }
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.QUESTION_MARK, beginIndex, endIndex);
            } else if (c == '[') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.LEFT_BRACKET, beginIndex, endIndex);
            } else if (c == ']') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.RIGHT_BRACKET, beginIndex, endIndex);
            } else if (c == '^') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.CIRCUMFLEX, beginIndex, endIndex);
            } else if (c == '_') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.UNDERSCORE, beginIndex, endIndex);
            } else if (c == '|') {
                c = read();
                if (c == '|') {
                    int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                    int endIndex = bufferBeginIndex + bufferCurrentIndex;
                    return new Token(TokenType.CONCATENATION_OPERATOR, beginIndex, endIndex);
                }
                back();
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.VERTICAL_BAR, beginIndex, endIndex);
            } else if (c == '{') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.LEFT_BRACE, beginIndex, endIndex);
            } else if (c == '}') {
                int beginIndex = bufferBeginIndex + bufferTokenBeginIndex;
                int endIndex = bufferBeginIndex + bufferCurrentIndex;
                return new Token(TokenType.RIGHT_BRACE, beginIndex, endIndex);
            } else {
                throw new YuliaException(getSql(), getLineNumber(), bufferCurrentIndex - 1, "非法字符。");
            }
        }
    }

    public void close() {
        if (reader == null) {
            return;
        }
        try {
            reader.close();
            reader = null;
        } catch (IOException e) {
            throw new RuntimeException("关闭字符流失败。", e);
        }
    }

    private int load() {
        int i;
        int c;
        for (i = 0; i < BUFFER_INIT_CAPACITY; i++) {
            try {
                c = reader.read();
            } catch (IOException e) {
                throw new RuntimeException("读取SQL语句失败。", e);
            }
            if (c < 0) {
                break;
            }
            buffer.append((char) c);
        }
        return i + 1;
    }

    /**
     * 从缓存区中读取一个字符。
     */
    private int read() {
        if (bufferCurrentIndex >= buffer.length()) {
            load();
        }
        if (bufferCurrentIndex >= buffer.length()) {
            return -1;
        }
        return buffer.charAt(bufferCurrentIndex++);
    }

    /**
     * 从缓存区中退回一个字符。
     */
    private void back() {
        bufferCurrentIndex--;
    }

    /**
     * 跳过空白字符。
     */
    private int skip() {
        while (true) {
            if (bufferCurrentIndex >= buffer.length()) {
                load();
            }
            if (bufferCurrentIndex >= buffer.length()) {
                return -1;
            }
            int c = buffer.charAt(bufferTokenBeginIndex = bufferCurrentIndex++);
            if (!Character.isWhitespace(c)) {
                return c;
            }
        }
    }

    private int charAt(int index) {
        if (index >= buffer.length()) {
            load();
        }
        if (index >= buffer.length()) {
            return -1;
        }
        return buffer.charAt(index);
    }

    void clean() {
        for (int i = 0; i < bufferCurrentIndex; i++) {
            int c = buffer.charAt(i);
            if (c == '\n') {
                lineNumber++;
                continue;
            }
            if (c == '\r') {
                lineNumber++;
                int c1 = charAt(++i);
                if (c1 < 0) {
                    break;
                }
                if (c1 == '\r') {
                    i--;
                    continue;
                }
                if (c1 == '\n') {
                    if (i >= bufferCurrentIndex && i + 1 <= buffer.length()) {
                        bufferCurrentIndex = i + 1;
                    }
                }
            }
        }
        buffer.delete(0, bufferCurrentIndex);
        bufferBeginIndex += bufferCurrentIndex;
        bufferCurrentIndex = 0;
    }

    public String getSql() {
        return buffer.toString();
    }

    public int getLineNumber() {
        return lineNumber;
    }

}
